package abolatw.phprpc.annotation;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.phprpc.PHPRPC_Server;

/**
 * 讓PHPRPC Server 可透過 Annotation宣告使用的 PHPRPC 擴充功能
 * 
 * 透過Filter分析requestURI是否符 @PHPRPC 發佈的對象
 * 
 * 發佈 class example:    
 * @PHPRPC( urlPattern="/PhprpcTest.phprpc" )
 * public class MyClass{
 * 	... 
 * }
 * 
 * 
 * 設定相關及文件請參考 
 * @see http://abolatw.blogspot.com/2012/10/phprpc-in-java-annotation.html
 * 
 * @author Abola Lee <abola921@gmail.com> 
 */
@SuppressWarnings({"unchecked", "rawtypes"})
public class PHPRPC_Filter implements Filter{
	
	private FilterConfig filterConfig;
	
	/**
	 * 於 web.xml 中設定  scan含有 @PHPRPC 類別的 package 起點  
	 */
	private static String PHPRPC_BEAN_SCAN_PATH = "phprpcBeanScanPath";

	/**
	 * 
	 */
	private static Map<String, Class<?>> listenUrlPatterns = null;
	
	/**
	 * 掃描指定 package 下所有的class 是否有 @PHPRPC 
	 */
	private synchronized void scan () throws ServletException{
		// 已有 scan 過
		if ( null != listenUrlPatterns ) return;
		
		String scanPath = this.filterConfig.getInitParameter( PHPRPC_BEAN_SCAN_PATH );
		
		if ( null == scanPath || "".equals(scanPath) ){
			throw new ServletException("PHPRPC_Filter must setting param: " + PHPRPC_BEAN_SCAN_PATH + ". check your web.xml. ");
		}
		
		Set<Class> phprpcClassSet = new HashSet<Class>();
		// 將所有 scanPath 下有  @PHPRPC 設定的Class 取出
		for( Class clazz : getClassesX( scanPath ) ){
			if ( clazz.isAnnotationPresent( PHPRPC.class ) ){
				phprpcClassSet.add(clazz);
			}
		}
		
		listenUrlPatterns = new HashMap<String, Class<?>>();
		
		// 包裝成以 urlPattern 為key 的map
		for( Class clazz : phprpcClassSet ){
			PHPRPC phprpc = (PHPRPC) clazz.getAnnotation(PHPRPC.class);
			listenUrlPatterns.put(phprpc.urlPattern() , clazz );
		}

	}
	
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		this.filterConfig = filterConfig;
		// 第一次執行
		if ( null == listenUrlPatterns ){
			scan();
		}
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain filterChain) throws IOException, ServletException {
		
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;

		String urlPattern = httpRequest.getRequestURI()
							// remove context path
							.substring( httpRequest.getContextPath().length() ) ;

		
		if ( listenUrlPatterns.containsKey( urlPattern ) ){	// 符合 @PHPRPC 對象
			
			Class clazz = listenUrlPatterns.get(urlPattern);
			
			PHPRPC phprpc = (PHPRPC) clazz.getAnnotation( PHPRPC.class );
			PHPRPC_Server server = new PHPRPC_Server();
			try{
				if ( 0 == phprpc.add().length ){	// 無指定發佈 method
					if( PHPRPC_MethodType.STATIC == phprpc.methodType() ){
						server.add( clazz );
					}
					else if( PHPRPC_MethodType.INSTANCE == phprpc.methodType() ){
						server.add( clazz.newInstance() );
					}
					else { // both
						server.add( clazz );
						server.add( clazz.newInstance() );
					}
				}
				else{	// 有指定發佈的 method 
					if( PHPRPC_MethodType.STATIC == phprpc.methodType() ){
						server.add( phprpc.add(), clazz );
					}
					else if( PHPRPC_MethodType.INSTANCE == phprpc.methodType() ){
						server.add( phprpc.add(), clazz.newInstance() );
					}
					else { // both
						server.add( phprpc.add(), clazz );
						server.add( phprpc.add(), clazz.newInstance() );
					}
				}
			}catch(Exception e){
				throw new ServletException(e);
			}
			
			// PHPRPC server start
			server.start(httpRequest, httpResponse);
		}
		else {
			filterChain.doFilter(request, response);
		}
	}

	
	@Override
	public void destroy() {
		this.filterConfig = null;
	}
	
	/**
	 * 列出指定package 下所有的class
	 * 
	 * Reference "自動列出某package之下所有的class名稱"
	 * http://www.javaworld.com.tw/jute/post/view?bid=29&id=273108
	 * @author xlinx 
	 */
	private static Class[] getClassesX(String packageName) {
		Class[] returnClasses = null;
		try {
			ClassLoader classLoader = Thread.currentThread()
					.getContextClassLoader();
			assert classLoader != null;
			String path = packageName.replace('.', '/');
			Enumeration<URL> resources;
			resources = classLoader.getResources(path);
			List<File> dirs = new ArrayList<File>();
			while (resources.hasMoreElements()) {
				URL resource = resources.nextElement();
				dirs.add(new File(resource.getFile()));
			}
			ArrayList<Class> classes = new ArrayList<Class>();
			for (File directory : dirs) {
				classes.addAll(findClassesX(directory, packageName));
			}
			returnClasses = classes.toArray(new Class[classes.size()]);
			return returnClasses;

		} catch (Exception e) {
			e.printStackTrace();

		}
		return returnClasses;

	}

	
	private static List<Class> findClassesX(File directory, String packageName)
			throws ClassNotFoundException {
		List<Class> classes = new ArrayList<Class>();
		if (!directory.exists()) {
			return classes;
		}
		File[] files = directory.listFiles();
		for (File file : files) {
			if (file.isDirectory()) {
				assert !file.getName().contains(".");
				classes.addAll(findClassesX(file,
						packageName + "." + file.getName()));
			} else if (file.getName().endsWith(".class")) {
				classes.add(Class.forName(packageName
						+ '.'
						+ file.getName().substring(0,
								file.getName().length() - 6)));
			}
		}
		return classes;
	}
}
